/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: MatchingAnalyzer.java 502 2007-05-04 15:08:19Z jacek.kolezynski $
 */
package logmatcher.analyzers;

import logmatcher.Configurable;
import logmatcher.LogEventListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.log4j.spi.LoggingEvent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
*  Abstract class for matching elements. Supplies a skeleton of basic operations and algorithm of processing
*  messages.
*
*  @version $Revision: 502 $
 */
public abstract class MatchingAnalyzer implements Configurable, LogEventListener {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    /** Class logger. */
    private static final Log LOGGER = LogFactory.getLog(MatchingAnalyzer.class);

    //~ Instance fields ------------------------------------------------------------------------------------------------

    /** String id pattern used to extract pair identifier. */
    protected String id;

    /** Pattern used to extract pair identifier. */
    protected Pattern idPattern;

    /** Determines if to log messages containing id, but not matching any of sequence elements. */
    protected boolean showInvalidMessages;

    /** Flag telling if the analysis is stopped. */
    protected boolean stopped = true;

    //~ Constructors ---------------------------------------------------------------------------------------------------

/**
     * Creates a new PairMatchingAnalyzer object.
     */
    public MatchingAnalyzer() {
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    /**
    *  Gets a map of matched elements.
    *
    *  @return map of matched elements.
    */
    public abstract Map<String, MatchingElement> getMatchedElements();


    /**
    *  Gets a deep copy of the analyzed sequence.
    *
    *  @param id the id to be set to the sequence
    *
    *  @return deep copy of the analyzed sequence
    */
    public abstract MatchingElement getSequenceCopy(String id);


    /**
    *  Gets a map of unmatched elements.
    *
    *  @return map of unmatched elements.
    */
    public abstract Map<String, MatchingElement> getUnmatchedElements();


    /**
     *  @todo DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    */
    public String getIdPattern() {
        return id;
    }


    /**
     *  @todo DOCUMENT ME!
    *
    *  @return TODO: DOCUMENT ME!
    */
    public boolean isShowInvalidMessages() {
        return showInvalidMessages;
    }


    /**
    *  Prints report of the analysis.
    */
    public void printReport() {
        LOGGER.info("Number of unmatched elements: " + this.getUnmatchedElements().size());

        Set<String> keys = getUnmatchedElements().keySet();
        StringBuilder sb = new StringBuilder();
        sb.append("Unmatched ids: ");

        for (String s : keys) {
            sb.append(s);
            sb.append(",");
        }

        LOGGER.info("Number of matched elements: " + this.getMatchedElements().size());

        keys = getMatchedElements().keySet();
        sb.append("\nMatched ids: ");

        for (String s : keys) {
            sb.append(s);
            sb.append(",");
        }

        LOGGER.info(sb.toString());
    }


    /**
    *  Process received message.
    *
    *  @param loggingEvent incoming object.
    */
    public void receiveLogEvent(Object loggingEvent) {
        if (stopped) {
            return;
        }

        String msg;
        LOGGER.debug("receiveLogEvent =" + loggingEvent);

        if (loggingEvent instanceof LoggingEvent) {
            LoggingEvent event = (LoggingEvent)loggingEvent;
            msg = String.valueOf(event.getMessage());
        } else {
            msg = (String)loggingEvent;
        }

        LOGGER.debug("the message = " + msg);

        Matcher matcher = idPattern.matcher(msg);
        LOGGER.debug("checking the id pattern = " + idPattern.pattern());

        if (matcher.find()) {
            LOGGER.debug("this LogEvent matches id.");

            // extract message id
            String id = matcher.group();
            boolean newElement = false;
            MatchingElement me = getUnmatchedElements().get(id);

            if (me == null) {
                if (getMatchedElements().containsKey(id)) {
                    me = getMatchedElements().get(id);
                } else {
                    me = getSequenceCopy(id);
                    LOGGER.debug("MatchingAnalyzer - sequence = " + me);
                    newElement = true;
                }
            }

            me.check(msg);
            LOGGER.debug("after checking = " + me);

            if (me.isSuccess() && !getMatchedElements().containsKey(id)) {
                LOGGER.debug("sequence matching COMPLETE adding to matched elements.");
                getUnmatchedElements().remove(id);
                getMatchedElements().put(id, me);
            } else if (newElement) {
                // if new pair is unmatched, put it into the map
                LOGGER.debug("sequence matching NOT COMPLETE, adding to unmatched elements");
                getUnmatchedElements().put(id, me);
            }
        } else {
            if (showInvalidMessages) {
                LOGGER.warn("Skipped message: " + msg);
            }
        }
    }


    /**
     *  @todo DOCUMENT ME!
    *
    *  @param id TODO: DOCUMENT ME!
    */
    public void setIdPattern(String id) {
        this.idPattern = Pattern.compile(id);
    }


    /**
     *  @todo DOCUMENT ME!
    *
    *  @param showInvalidMessages TODO: DOCUMENT ME!
    */
    public void setShowInvalidMessages(boolean showInvalidMessages) {
        this.showInvalidMessages = showInvalidMessages;
    }


    /**
    *  Starts the analysis.
    */
    public void start() {
        stopped = false;
    }


    /**
    *  Stops the analysis. Stops collecting events and stops all elements.
    */
    public void stop() {
        stopped = true;

        Collection<MatchingElement> temp = new ArrayList<MatchingElement>();
        Collection<MatchingElement> values = getUnmatchedElements().values();

        for (MatchingElement element : values) {
            if (!element.isFinished()) {
                element.stop();
            }

            if (element.isSuccess()) {
                temp.add(element);
            }
        }

        for (Iterator iter = temp.iterator(); iter.hasNext();) {
            MatchingElement element = (MatchingElement)iter.next();
            getUnmatchedElements().remove(element.getId());
            getMatchedElements().put(element.getId(), element);
        }
    }
}
